import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext, filedialog
import threading
import sys
import pandas as pd
import os
import logging
import time
from PIL import Image, ImageTk, ImageDraw
import urllib.request
import io
import hashlib
from typing import List, Dict, Optional
import queue
import shutil

# 添加项目根目录到路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

try:
    from threaded_crawler import MultiThreadedDouyinCrawler, get_global_crawler
    from crawler import DouyinCrawler
    from logger import logger
    from tencent_gender_detection import detect_gender_from_image_api
except ImportError as e:
    print(f"导入模块失败: {e}")
    MultiThreadedDouyinCrawler = None
    get_global_crawler = None
    DouyinCrawler = None
    logger = None
    detect_gender_from_image_api = None


class EnhancedDouyinCrawlerGUI:
    """增强版抖音爬虫GUI，支持多线程"""
    
    def __init__(self, root=None):
        # 初始化变量
        self.comments_data = []
        self.extract_type = "pinglun"
        self.avatar_cache = {}
        self.is_processing = False
        self.crawler_thread = None
        self.threaded_crawler = None
        self._update_queue = queue.Queue()  # 用于线程间通信
        self._update_job = None  # 用于存储after回调ID
        self.avatar_load_queue = queue.Queue()  # 用于异步加载头像
        self.placeholder_image = self.create_placeholder_image()  # 创建占位符图像
        self.crawler_instance = None  # 当前爬虫实例
        self.images_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'images')
        self.setup_images_directory()  # 设置并清空images目录

        # 设置主窗口
        self.root = root if root else tk.Tk()
        self.root.title("抖音评论爬虫工具 - 多线程版")
        self.root.geometry("1200x600")
        self.root.minsize(900, 900)
        
        # 在创建小部件之前设置样式
        self.setup_styles()
        
        # 创建界面
        self.create_widgets()
        
        # 启动更新循环和头像加载线程
        self.start_update_loop()
        self.start_avatar_workers()
        
    def setup_images_directory(self):
        """设置并清空images目录"""
        # 创建images目录（如果不存在）
        if not os.path.exists(self.images_dir):
            try:
                os.makedirs(self.images_dir)
                if logger:
                    logger.info(f"创建images目录: {self.images_dir}")
            except Exception as e:
                if logger:
                    logger.error(f"创建images目录失败: {e}")
        else:
            # 清空images目录下的所有文件
            try:
                for filename in os.listdir(self.images_dir):
                    file_path = os.path.join(self.images_dir, filename)
                    try:
                        if os.path.isfile(file_path) or os.path.islink(file_path):
                            os.unlink(file_path)
                        elif os.path.isdir(file_path):
                            shutil.rmtree(file_path)
                    except Exception as e:
                        if logger:
                            logger.error(f'删除文件 {file_path} 失败: {e}')
                if logger:
                    logger.info(f"已清空images目录: {self.images_dir}")
            except Exception as e:
                if logger:
                    logger.error(f"清空images目录失败: {e}")
        
    def after(self, delay, callback):
        """转发after调用到root窗口"""
        return self.root.after(delay, callback)
        
    def on_browser_closed(self):
        """浏览器关闭回调方法，用于更新按钮状态"""
        # 使用after方法确保在主线程中更新GUI
        self.root.after(0, self._update_buttons_after_browser_closed)
        
    def _update_buttons_after_browser_closed(self):
        """在浏览器关闭后更新按钮状态"""
        # 重置处理状态
        self.is_processing = False
        
        # 更新控制按钮状态
        self.update_control_buttons()
        
        # 确保导出按钮根据数据存在与否来设置状态
        # if self.comments_data:
        #     self.export_button.config(state=tk.NORMAL)
        # else:
        #     self.export_button.config(state=tk.DISABLED)
            
        # 更新状态标签
        self.log_message("浏览器已关闭，采集完成\n")
        
    def create_placeholder_image(self):
        """创建一个100x100的灰色占位符图像"""
        size = (100, 100)
        # 创建一个灰色背景的图像
        img = Image.new('RGB', size, (220, 220, 220))
        # 在图像中心绘制一个更深的灰色圆圈
        draw = ImageDraw.Draw(img)
        draw.ellipse((15, 15, 85, 85), fill=(200, 200, 200))
        return ImageTk.PhotoImage(img)

    def start_avatar_workers(self):
        """启动后台工作线程来加载头像"""
        num_workers = 4  # 可以根据需要调整工作线程数
        for _ in range(num_workers):
            worker = threading.Thread(target=self.avatar_worker)
            worker.daemon = True
            worker.start()

    def avatar_worker(self):
        """头像加载和性别检测工作线程"""
        while True:
            item_id, avatar_url, index = self.avatar_load_queue.get()
            if item_id is None:  # 停止信号
                break
                
            try:
                # 加载头像
                photo = self.get_avatar_image(avatar_url, index)
                
                # 如果加载成功，则将结果放入更新队列
                if photo:
                    self._update_queue.put(('avatar_loaded', (item_id, photo)))
                    
                
            except Exception as e:
                if logger:
                    logger.error(f"处理头像或性别检测任务时出错: {e}")
                # 将错误信息放入更新队列，以便在GUI中显示
                self._update_queue.put(('avatar_error', (item_id, str(e))))
            
            self.avatar_load_queue.task_done()

    def setup_styles(self):
        """设置界面样式"""
        style = ttk.Style()
        
        # 配置整体主题
        style.theme_use('clam')
        
        # 配置标签帧样式
        style.configure(
            "Main.TLabelframe",
            background="#f5f6fa",
            foreground="#2f3542",
            font=("微软雅黑", 11, "bold"),
            borderwidth=2,
            relief="groove"
        )
        style.configure(
            "Main.TLabelframe.Label",
            background="#f5f6fa",
            foreground="#2f3542",
            font=("微软雅黑", 11, "bold")
        )
        
        # 配置按钮样式
        style.configure(
            "Action.TButton",
            background="#3742fa",
            foreground="white",
            font=("微软雅黑", 10, "bold"),
            padding=8,
            borderwidth=0,
            relief="flat"
        )
        style.map(
            "Action.TButton",
            background=[('active', '#5352ed'), ('pressed', '#1e90ff')],
            foreground=[('active', 'white'), ('pressed', 'white')]
        )
        
        # 配置次要按钮样式
        style.configure(
            "Secondary.TButton",
            background="#a4b0be",
            foreground="white",
            font=("微软雅黑", 9),
            padding=6,
            borderwidth=0
        )
        style.map(
            "Secondary.TButton",
            background=[('active', '#747d8c'), ('pressed', '#57606f')],
            foreground=[('active', 'white'), ('pressed', 'white')]
        )
        
        # 配置危险操作按钮样式
        style.configure(
            "Danger.TButton",
            background="#ff4757",
            foreground="white",
            font=("微软雅黑", 9),
            padding=6,
            borderwidth=0
        )
        style.map(
            "Danger.TButton",
            background=[('active', '#ff3742'), ('pressed', '#e84118')],
            foreground=[('active', 'white'), ('pressed', 'white')]
        )
        
        # 配置标签样式
        style.configure(
            "Header.TLabel",
            background="#f5f6fa",
            foreground="#2f3542",
            font=("微软雅黑", 14, "bold")
        )
        
        style.configure(
            "Normal.TLabel",
            background="#f5f6fa",
            foreground="#57606f",
            font=("微软雅黑", 10)
        )
        
        # 配置输入框样式
        style.configure(
            "Input.TEntry",
            fieldbackground="white",
            foreground="#2f3542",
            font=("微软雅黑", 10),
            borderwidth=1,
            relief="solid"
        )
        
        # 配置Treeview样式
        style.configure(
            "Results.Treeview",
            background="white",
            foreground="#2f3542",
            rowheight=100,
            font=("微软雅黑", 9),
            borderwidth=0,
            relief="flat"
        )
        style.configure(
            "Results.Treeview.Heading",
            background="#dfe4ea",
            foreground="#2f3542",
            font=("微软雅黑", 9, "bold"),
            borderwidth=0,
            relief="flat",
            padding=8
        )
        style.map(
            "Results.Treeview",
            background=[('selected', '#3742fa')],
            foreground=[('selected', 'white')]
        )
        
    def create_widgets(self):
        """创建界面组件"""
        # 主要内容区域 - 左右分栏
        main_frame = tk.Frame(self.root, bg='#f5f6fa')
        main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)
        
        # 左侧区域 (35% 宽度)
        left_frame = tk.Frame(main_frame, bg='#f5f6fa')
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=False, padx=(0, 15))
        left_frame.configure(width=420)
        left_frame.pack_propagate(False)
        
        # 右侧区域 (65% 宽度)
        right_frame = tk.Frame(main_frame, bg='#f5f6fa')
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
        
        # 左侧内容
        self.create_left_panel(left_frame)
        
        # 右侧内容
        self.create_right_panel(right_frame)
        
        # 底部状态栏
        status_frame = tk.Frame(self.root, bg='#dfe4ea', height=40)
        status_frame.pack(fill=tk.X, padx=20, pady=(0, 20))
        status_frame.pack_propagate(False)
        
        self.status_label = ttk.Label(
            status_frame,
            text="就绪 - 等待开始采集",
            style="Normal.TLabel",
            background="#dfe4ea",
            font=("微软雅黑", 9)
        )
        self.status_label.pack(side=tk.LEFT, padx=15, pady=10)
        
        # 进度条
        self.progress_bar = ttk.Progressbar(
            status_frame,
            mode='indeterminate',
            length=200
        )
        self.progress_bar.pack(side=tk.RIGHT, padx=15)
        
        # 添加版本信息
        version_label = ttk.Label(
            status_frame,
            text="v2.0 - 多线程版",
            style="Normal.TLabel",
            background="#dfe4ea",
            foreground="#a4b0be"
        )
        version_label.pack(side=tk.RIGHT, padx=15)
        
    def create_left_panel(self, parent):
        """创建左侧操作面板"""
        # 多线程设置区域
        thread_frame = ttk.LabelFrame(
            parent,
            text="多线程设置",
            style="Main.TLabelframe"
        )
        thread_frame.pack(fill=tk.X, pady=(0, 15))
        
        # 线程数设置
        thread_control_frame = tk.Frame(thread_frame, bg='#f5f6fa')
        thread_control_frame.pack(fill=tk.X, padx=15, pady=10)
        
        ttk.Label(
            thread_control_frame,
            text="工作线程数:",
            style="Normal.TLabel"
        ).pack(side=tk.LEFT)
        
        self.thread_var = tk.StringVar(value="3")
        self.thread_spinbox = tk.Spinbox(
            thread_control_frame,
            from_=1,
            to=10,
            textvariable=self.thread_var,
            width=3,
            font=("微软雅黑", 10),
            bg="white",
            fg="#2f3542",
            bd=1,
            relief=tk.GROOVE
        )
        self.thread_spinbox.pack(side=tk.LEFT, padx=10)
        
        # # 批量模式复选框
        # self.batch_mode_var = tk.BooleanVar(value=False)
        # self.batch_checkbox = ttk.Checkbutton(
        #     thread_control_frame,
        #     text="批量模式",
        #     variable=self.batch_mode_var,
        #     command=self.toggle_batch_mode
        # )
        # self.batch_checkbox.pack(side=tk.LEFT, padx=10)
        
        # 视频链接输入区域
        link_frame = ttk.LabelFrame(
            parent,
            text="视频链接设置",
            style="Main.TLabelframe"
        )
        link_frame.pack(fill=tk.X, pady=(0, 15))
        
        # 单链接模式
        self.single_link_frame = tk.Frame(link_frame, bg='#f5f6fa')
        self.single_link_frame.pack(fill=tk.X, padx=15, pady=10)
        
        ttk.Label(
            self.single_link_frame,
            text="TikTok视频链接:",
            style="Normal.TLabel"
        ).pack(anchor=tk.W)
        
        self.url_entry = ttk.Entry(
            self.single_link_frame,
            width=50,
            style="Input.TEntry",
            font=("微软雅黑", 10)
        )
        self.url_entry.insert(0, "https://www.tiktok.com/@hatsu483/video/7570271397428219156")
        self.url_entry.pack(fill=tk.X, pady=(5, 10))
        
        # 操作按钮区域
        button_frame = ttk.LabelFrame(
            parent,
            text="操作控制",
            style="Main.TLabelframe"
        )
        button_frame.pack(fill=tk.X, pady=(0, 15))
        
        # 按钮容器
        buttons_container = tk.Frame(button_frame, bg='#f5f6fa')
        buttons_container.pack(fill=tk.X, padx=15, pady=10)
        
        # 第一行按钮
        row1_buttons = tk.Frame(buttons_container, bg='#f5f6fa')
        row1_buttons.pack(fill=tk.X, pady=5)
        
        self.start_button = ttk.Button(
            row1_buttons,
            text="采集评论",
            style="Action.TButton",
            command=lambda: self.start_threaded_crawling("pinglun")
        )
        self.start_button.pack(side=tk.TOP, fill=tk.X, pady=2)
        self.guanzhu_button = ttk.Button(
            row1_buttons,
            text="采集关注",
            style="Action.TButton",
            command=lambda: self.start_threaded_crawling("guanzhu")
        )
        self.guanzhu_button.pack(side=tk.TOP, fill=tk.X, pady=2)
        # 控制按钮行
        control_row = tk.Frame(buttons_container, bg='#f5f6fa')
        control_row.pack(fill=tk.X, pady=5)
        
        self.stop_button = ttk.Button(
            control_row,
            text="停止",
            style="Danger.TButton",
            command=self.stop_crawling,
            state=tk.DISABLED
        )
        self.stop_button.pack(side=tk.LEFT, fill=tk.X, expand=True)
        
        # 导出按钮
        self.export_button = ttk.Button(
            buttons_container,
            text="导出为Excel",
            style="Action.TButton",
            command=self.export_to_excel,
            state=tk.NORMAL
        )
        self.export_button.pack(side=tk.TOP, fill=tk.X, pady=8)
        
        # 滚动次数设置
        scroll_frame = tk.Frame(button_frame, bg='#f5f6fa')
        scroll_frame.pack(fill=tk.X, padx=15, pady=(15, 10))
        
        ttk.Label(
            scroll_frame,
            text="滚动次数设置:",
            style="Normal.TLabel",
            font=("微软雅黑", 10, "bold")
        ).pack(anchor=tk.W)
        
        self.scroll_var = tk.StringVar(value="100")
        self.scroll_spinbox = tk.Spinbox(
            scroll_frame,
            from_=1,
            to=20,
            textvariable=self.scroll_var,
            width=5,
            font=("微软雅黑", 11),
            bg="white",
            fg="#2f3542",
            bd=1,
            relief=tk.GROOVE
        )
        self.scroll_spinbox.pack(pady=10)
        
         # 日志显示区域
        log_frame = ttk.LabelFrame(
            parent,
            text="运行日志",
            style="Main.TLabelframe"
        )
        log_frame.pack(fill=tk.BOTH, expand=True)
        
        self.log_text = scrolledtext.ScrolledText(
            log_frame,
            height=8,
            font=("Consolas", 8),
            bg="#2f3542",
            fg="#ffffff",
            bd=0,
            relief=tk.FLAT
        )
        self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 重定向日志输出到文本框
        self.setup_logging()
        # 统计信息显示区域
        stats_frame = ttk.LabelFrame(
            parent,
            text="采集统计",
            style="Main.TLabelframe"
        )
        stats_frame.pack(fill=tk.X, pady=(0, 15))
        
        stats_container = tk.Frame(stats_frame, bg='#f5f6fa')
        stats_container.pack(fill=tk.X, padx=15, pady=10)
        
        # 统计标签
        self.stats_labels = {}
        stats_items = [
            ("总任务数", "total_tasks"),
            ("已完成", "completed"),
            ("进行中", "running"),
            ("失败", "failed"),
            ("总评论数", "total_comments")
        ]
        
        for i, (label_text, key) in enumerate(stats_items):
            frame = tk.Frame(stats_container, bg='#f5f6fa')
            frame.pack(fill=tk.X, pady=2)
            
            ttk.Label(
                frame,
                text=f"{label_text}:",
                style="Normal.TLabel",
                font=("微软雅黑", 9)
            ).pack(side=tk.LEFT)
            
            self.stats_labels[key] = ttk.Label(
                frame,
                text="0",
                style="Normal.TLabel",
                font=("微软雅黑", 9, "bold"),
                foreground="#3742fa"
            )
            self.stats_labels[key].pack(side=tk.RIGHT)
        
       
        
    def create_right_panel(self, parent):
        """创建右侧结果显示面板"""
        # 结果显示区域
        result_frame = ttk.LabelFrame(
            parent,
            text="采集结果",
            style="Main.TLabelframe"
        )
        result_frame.pack(fill=tk.BOTH, expand=True)
        
        # 添加顶部操作按钮
        top_frame = tk.Frame(result_frame, bg='#f5f6fa')
        top_frame.pack(fill=tk.X, padx=5, pady=5)
        
        # 实时更新计数
        self.item_count_label = ttk.Label(
            top_frame,
            text="显示: 0 条",
            style="Normal.TLabel"
        )
        self.item_count_label.pack(side=tk.LEFT, padx=10)
        
        # 选择操作按钮
        select_all_button = ttk.Button(
            top_frame,
            text="全选",
            style="Secondary.TButton",
            command=self.select_all_items
        )
        select_all_button.pack(side=tk.LEFT, padx=5)
        
        deselect_all_button = ttk.Button(
            top_frame,
            text="取消全选",
            style="Secondary.TButton",
            command=self.deselect_all_items
        )
        deselect_all_button.pack(side=tk.LEFT, padx=5)
        
        invert_selection_button = ttk.Button(
            top_frame,
            text="反选",
            style="Secondary.TButton",
            command=self.invert_selection
        )
        invert_selection_button.pack(side=tk.LEFT, padx=5)
        # 添加查重按钮
        # duplicate_button = ttk.Button(
        #     top_frame,
        #     text="查重",
        #     style="Secondary.TButton",
        #     command=self.search_in_comments_gui
        # )
        # duplicate_button.pack(side=tk.LEFT, padx=5)
        # 添加ai识别按钮
        ai_button = ttk.Button(
            top_frame,
            text="AI识别",
            style="Danger.TButton",
            command=self.ai_identify_gender
        )
        ai_button.pack(side=tk.LEFT, padx=5)
        
        delete_button = ttk.Button(
            top_frame,
            text="删除选中项",
            style="Danger.TButton",
            command=self.delete_selected_items
        )
        delete_button.pack(side=tk.RIGHT, padx=5)
        
        # 创建包含Treeview和滚动条的容器
        tree_container = tk.Frame(result_frame, bg='#f5f6fa')
        tree_container.pack(fill=tk.BOTH, expand=True, padx=5, pady=(0, 5))
        
        # 创建Treeview来显示结果
        columns = ("avatar", "nickname", "user_link", "time","sex")
        self.result_tree = ttk.Treeview(
            tree_container,
            columns=columns,
            show="tree headings",
            style="Results.Treeview",
            selectmode="extended"
        )
        
        # 设置列标题
        self.result_tree.heading("avatar", text="头像")
        self.result_tree.heading("nickname", text="昵称")
        self.result_tree.heading("user_link", text="用户ID")
        self.result_tree.heading("time", text="评论时间")
        self.result_tree.heading("sex", text="性别")
        
        # 设置列宽
        self.result_tree.column("#0", width=100, minwidth=105, stretch=tk.NO)
        self.result_tree.column("avatar", width=100, minwidth=100, stretch=tk.NO)
        self.result_tree.column("nickname", width=120, minwidth=100)
        self.result_tree.column("user_link", width=100, minwidth=150)
        self.result_tree.column("time", width=120, minwidth=100)
        self.result_tree.column("sex", width=120, minwidth=100)
        
        # 添加滚动条
        scrollbar_y = ttk.Scrollbar(
            tree_container,
            orient=tk.VERTICAL,
            command=self.result_tree.yview
        )
        scrollbar_x = ttk.Scrollbar(
            tree_container,
            orient=tk.HORIZONTAL,
            command=self.result_tree.xview
        )
        self.result_tree.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)
        
        # 绑定事件
        self.result_tree.bind('<Delete>', self.delete_selected_items)
        self.result_tree.bind('<BackSpace>', self.delete_selected_items)
        
        # 布局
        self.result_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)
        scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)
    
    # AI识别功能实现
    def ai_identify_gender(self):
        """AI识别功能实现 - 识别用户头像性别"""
        if not detect_gender_from_image_api:
            messagebox.showwarning("警告", "性别检测功能不可用，请检查依赖库是否正确安装")
            return
            
        items = self.result_tree.get_children()
        self.log_message(f"开始AI识别，共 {len(items)} 项\n")
        
        if len(items) == 0:
            self.log_message("没有需要识别的项目\n")
            return
            
        # 创建进度条窗口
        progress_window = tk.Toplevel(self.root)
        progress_window.title("AI识别进度")
        progress_window.geometry("300x100")
        progress_window.resizable(False, False)
        
        # 使进度条窗口居中显示在主窗口中央
        progress_window.transient(self.root)
        progress_window.grab_set()
        self.center_window(progress_window, 300, 100)
        
        # 添加进度标签
        progress_label = tk.Label(progress_window, text="正在识别中...")
        progress_label.pack(pady=5)
        
        # 添加进度条
        progress_var = tk.DoubleVar()
        progress_bar = ttk.Progressbar(
            progress_window, 
            variable=progress_var, 
            maximum=100,
            length=250
        )
        progress_bar.pack(pady=5)
        
        # 添加进度文本
        progress_text = tk.Label(progress_window, text="0%")
        progress_text.pack(pady=5)
        
        # 设置GUI日志回调函数
        if detect_gender_from_image_api:
            from tencent_gender_detection import set_gui_log_callback
            set_gui_log_callback(self.log_message)
        
        # 更新进度条窗口
        progress_window.update()
        
        # 使用多线程并行处理提高识别速度
        def process_item(index, item):
            """处理单个项目的识别任务"""
            result = {"index": index, "item": item, "gender_result": None, "error": None}
            try:
                # 检查是否有头像缓存，使用index作为键
                if index in self.avatar_cache:
                    # 从缓存中获取图像数据和PhotoImage对象
                    image_data, photo_image = self.avatar_cache[index]
                    
                    # 调用性别检测API，直接使用缓存的图像数据
                    gender_result = detect_gender_from_image_api(image_data)

                    result["gender_result"] = gender_result
                else:
                    result["error"] = "无头像缓存"
            except Exception as e:
                result["error"] = str(e)
            return result
        
        # 使用线程池并行处理项目
        import concurrent.futures
        processed_items = 0
        results = []
        
        # 创建线程池，最大并发数为5
        with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
            # 提交所有任务
            future_to_index = {
                executor.submit(process_item, index, item): (index, item) 
                for index, item in enumerate(items)
            }
            
            # 收集结果
            for future in concurrent.futures.as_completed(future_to_index):
                try:
                    result = future.result()
                    results.append(result)
                    
                    # 更新进度
                    processed_items += 1
                    progress_percent = (processed_items / len(items)) * 100
                    progress_var.set(progress_percent)
                    progress_text.config(text=f"{processed_items}/{len(items)} ({progress_percent:.1f}%)")
                    progress_window.update()
                except Exception as e:
                    processed_items += 1
                    self.log_message(f"处理项目时出错: {str(e)}\n")
        
        # 处理结果
        success_count = 0
        for result in results:
            index = result["index"]
            item = result["item"]
            gender_result = result["gender_result"]
            error = result["error"]
            
            if gender_result:
                try:
                    # 提取性别信息
                    gender = gender_result.get('gender', 'Unknown')
                    confidence = gender_result.get('confidence', 0)
                    # 识别为女 选中当前项
                    if gender == "Female":
                        # 设置当前item为选中态 可以多选
                        self.result_tree.selection_add(item)
                    # 识别为汉字
                    gender = gender.replace('Female', '女').replace('Male', '男')
                    gender_text = f"{gender} ({confidence:.1f}%)"
                    
                    # 更新时间列为性别名称
                    self.result_tree.set(item, "sex", gender_text)
                    success_count += 1
                except Exception as e:
                    self.result_tree.set(item, "sex", "检测错误")
                    self.log_message(f"第 {index+1} 项处理结果时出错: {str(e)}\n")
            elif error:
                self.result_tree.set(item, "sex", "检测错误")
                self.log_message(f"第 {index+1} 项检测出错: {error}\n")
            else:
                self.result_tree.set(item, "sex", "检测失败")
                self.log_message(f"第 {index+1} 项无头像缓存，跳过\n")
        
        # 完成后关闭进度条窗口
        progress_window.destroy()
        
        self.log_message(f"AI识别完成，成功处理 {success_count}/{len(items)} 项\n")
    
    def center_window(self, window, width, height):
        """将窗口居中显示在屏幕中央"""
        # 获取屏幕尺寸
        screen_width = window.winfo_screenwidth()
        screen_height = window.winfo_screenheight()
        
        # 计算居中位置
        x = (screen_width - width) // 2
        y = (screen_height - height) // 2
        
        # 设置窗口位置和尺寸
        window.geometry(f'{width}x{height}+{x}+{y}')
    
    def setup_logging(self):
        """设置日志输出到GUI文本框"""
        class TextHandler:
            def __init__(self, text_widget, update_callback):
                self.text_widget = text_widget
                self.update_callback = update_callback
                
            def write(self, message):
                # 使用队列来避免线程冲突
                if self.update_callback:
                    self.update_callback(message)
                else:
                    # 备用方案：直接写入（可能不安全）
                    try:
                        self.text_widget.insert(tk.END, message)
                        self.text_widget.see(tk.END)
                    except:
                        pass
                        
            def flush(self):
                pass
                
        # 将print输出重定向到文本框
        sys.stdout = TextHandler(self.log_text, self.log_message)
        
        # 也重定向logger输出到文本框
        if logger:
            handler = logging.StreamHandler(TextHandler(self.log_text, self.log_message))
            formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
            handler.setFormatter(formatter)
            logger.addHandler(handler)
    
    def log_message(self, message):
        """线程安全地记录消息"""
        self._update_queue.put(('log', message))
    
    def start_update_loop(self):
        """启动更新循环"""
        self.process_update_queue()
    
    def process_update_queue(self):
        """处理更新队列"""
        try:
            while True:
                update_type, data = self._update_queue.get_nowait()
                
                if update_type == 'log':
                    # 记录日志
                    self.log_text.insert(tk.END, data)
                    self.log_text.see(tk.END)
                    
                elif update_type == 'new_comment':
                    # 实时更新单条评论
                    self.update_result_display(data)
                    
                elif update_type == 'stats':
                    # 更新统计
                    self.update_stats_display(data)
                    
                elif update_type == 'avatar_loaded':
                    # 更新头像
                    item_id, photo = data
                    if self.result_tree.exists(item_id):
                        self.result_tree.item(item_id, image=photo)
                        
                elif update_type == 'avatar_error':
                    # 处理头像加载错误
                    item_id, error_msg = data
                    if self.result_tree.exists(item_id):
                        self.log_message(f"头像加载错误 (ID: {item_id}): {error_msg}\n")

        except queue.Empty:
            pass
        
        # 安排下一次更新
        self._update_job = self.root.after(100, self.process_update_queue)
    
    def start_threaded_crawling(self, type):
        """开始多线程爬取"""
        self.extract_type = type
        if self.is_processing:
            messagebox.showwarning("警告", "采集正在进行中")
            return
            
        # 获取要爬取的URL
        urls = self.get_urls_to_crawl()
        if not urls:
            return
            
        # 获取设置
        try:
            scroll_times = int(self.scroll_var.get())
            max_workers = int(self.thread_var.get())
        except ValueError:
            messagebox.showerror("错误", "请输入有效的数字")
            return
            
        # 更新状态
        self.is_processing = True
        self.update_control_buttons()
        
        # 清空之前的数据
        self.comments_data.clear()
        # 初始化多线程爬虫（确保在调用clear_data之前初始化）
        if self.threaded_crawler is None:
            self.threaded_crawler = get_global_crawler(max_workers)
        # 多线程也需要清理
        self.threaded_crawler.clear_data()
       
        self.clear_display()
        
        # 初始化多线程爬虫
        self.threaded_crawler = get_global_crawler(max_workers)
        self.threaded_crawler.set_gui_callback(self.on_data_update, self)
        self.threaded_crawler.gui_root = self.root  # 设置GUI根窗口引用
        
        # 添加任务
        for url in urls:
            self.threaded_crawler.add_crawl_task(url, scroll_times)
        
        # 开始爬取（非阻塞）
        self.threaded_crawler.extract_type = self.extract_type
        self.threaded_crawler.start_crawling(blocking=False)
        
        # 启动监控线程
        self.monitor_thread = threading.Thread(target=self.monitor_progress)
        self.monitor_thread.daemon = True
        self.monitor_thread.start()
        
        self.log_message(f"开始多线程爬取，共 {len(urls)} 个任务，{max_workers} 个工作线程\n")
    
    def get_urls_to_crawl(self) -> List[str]:
        """获取要爬取的URL列表"""
        # 单链接模式
        url = self.url_entry.get().strip()
        if not url:
            messagebox.showwarning("警告", "请输入TikTok视频链接")
            return []
        if self.extract_type == "guanzhu":
            # 检查是否是有效的用户主页链接
            if "/video/" in url:
                # 从视频链接提取用户主页链接
                try:
                    # 获取@后面的用户名
                    start_index = url.index("@") + 1
                    end_index = url.index("/", start_index)
                    username = url[start_index:end_index]
                    user_homepage = f"https://www.tiktok.com/@{username}"
                except ValueError:
                    messagebox.showerror("错误", "无法从视频链接提取用户主页链接，请手动输入用户主页链接")
                    return
            elif "/@" in url:
                user_homepage = url
            else:
                messagebox.showerror("错误", "请输入有效的用户主页链接或视频链接")
                return
            url = user_homepage
        return [url]
    
    def monitor_progress(self):
        """监控进度"""
        while self.is_processing:
            if self.threaded_crawler:
                stats = self.threaded_crawler.get_progress_stats()
                self._update_queue.put(('stats', stats))
                
                # 检查是否所有任务都完成了
                if stats['total'] > 0 and stats['completed'] + stats['failed'] == stats['total']:
                    self.is_processing = False
                    self._update_queue.put(('log', f"\n所有任务完成！总共获取 {stats['total_comments']} 条评论\n"))
                    break
                    
            time.sleep(0.5)
    
    def stop_crawling(self):
        """停止爬取"""
        if not self.is_processing or not self.threaded_crawler:
            return  # 如果没有在处理或没有爬虫实例，则直接返回
            
        try:
            # 停止所有任务
            self.threaded_crawler.stop_all_tasks()
            # 关闭浏览器
            self.threaded_crawler.close()
            # 更新状态
            self.is_processing = False
            
            # 更新按钮和控件状态
            self.update_control_buttons()
            
            # 确保导出按钮根据现有数据启用
            if self.comments_data:
                self.export_button.config(state=tk.NORMAL)
                
            # 记录日志
            self.log_message("已停止所有爬取任务\n")
            
        except Exception as e:
            # 防止因异常导致GUI无响应
            self.log_message(f"停止任务时发生错误: {e}\n")
            
    def on_data_update(self, data: List[Dict], completed: bool = False):
        """数据更新回调"""
        self._update_queue.put(('data', data))
        
    def update_display_data(self, data: List[Dict]):
        """更新显示数据"""
        # 使用分批更新避免界面卡顿
        self._add_items_in_batches(data, 0)
        
    def update_stats_display(self, stats):
        """更新统计显示"""
        for key, label in self.stats_labels.items():
            if key in stats:
                label.config(text=str(stats[key]))
                
    def update_result_display(self, comments):
        """实时更新单条评论或多条评论显示"""
        # 如果传入的是单个字典，转换为列表
        if isinstance(comments, dict):
            comments = [comments]
            
        # 处理每条评论
        for comment in comments:
            # 插入新项到Treeview
            item_id = self.result_tree.insert("", "end", values=(
                "",  # avatar列（稍后异步加载）
                comment.get("nickname", ""),
                comment.get("user_link", ""),
                comment.get("time", ""),
                
            ))
            
            # 将头像加载任务放入队列
            avatar_url = comment.get("avatar_url", "")
            if avatar_url:
                # 为每条评论分配一个索引，用于缓存键
                index = len(self.comments_data)
                self.avatar_load_queue.put((item_id, avatar_url, index))
                # 同时存储avatar_url到comments_data中，以便后续AI识别使用
                comment["avatar_url"] = avatar_url
            
            # 将评论添加到数据列表
            self.comments_data.append(comment)
        
        # 更新统计信息
        self.update_total_count()
        
    def update_total_count(self):
        """更新总数统计"""
        if 'total' in self.stats_labels:
            self.stats_labels['total'].config(text=str(len(self.comments_data)))
            
    def update_control_buttons(self):
        """更新控制按钮状态"""
        if self.is_processing:
            if self.extract_type == "pinglun":
                self.start_button.config(state=tk.DISABLED,text="正在采集...")
            elif self.extract_type == "guanzhu":
                self.guanzhu_button.config(state=tk.DISABLED,text="正在采集...")
            self.stop_button.config(state=tk.NORMAL)
            self.export_button.config(state=tk.NORMAL)
            self.progress_bar.start()
        else:
            self.start_button.config(state=tk.NORMAL, text="采集评论")
            self.guanzhu_button.config(state=tk.NORMAL, text="采集关注")
            self.stop_button.config(state=tk.DISABLED)
            # self.export_button.config(state=tk.NORMAL if self.comments_data else tk.DISABLED)
            self.progress_bar.stop()
            
    def clear_display(self):
        """清空显示"""
        # 清空Treeview
        for item in self.result_tree.get_children():
            self.result_tree.delete(item)
        self.item_count_label.config(text="显示: 0 条")
        
    def _add_items_in_batches(self, data: List[Dict], start_index: int):
        """分批添加项目以避免阻塞GUI"""
        if start_index == 0:
            # 第一次调用，清空现有数据
            self.clear_display()
            self.comments_data[:] = data
        
        batch_size = 20  # 每批处理20个项目
        end_index = min(start_index + batch_size, len(data))
        
        # 添加一批数据
        for i in range(start_index, end_index):
            if i >= len(data):
                break
            comment = data[i]

            # 插入新项，使用占位符头像
            item_id = self.result_tree.insert("", "end", values=(
                "",  # avatar列（不使用）
                comment.get("nickname", ""),
                comment.get("time", ""),
                comment.get("user_link", "")
            ), tags=(i,), image=self.placeholder_image)
            
            # 将头像加载任务放入队列
            avatar_url = comment.get("avatar_url", "")
            if avatar_url:
                self.avatar_load_queue.put((item_id, avatar_url, i))
        
        # 更新计数
        current_count = len(self.result_tree.get_children())
        self.item_count_label.config(text=f"显示: {current_count} 条")
        
        # 如果还有数据需要处理，安排下一批
        if end_index < len(data):
            self.root.after_idle(lambda: self._add_items_in_batches(data, end_index))
        else:
            # 更新控制按钮
            self.update_control_buttons()
            
    def search_comments(self):
        """搜索评论"""
        keyword = self.search_var.get().strip()
        if not keyword:
            # 显示所有数据
            self.update_display_data(self.comments_data)
            return
            
        # 搜索匹配的数据
        matched_comments = []
        for comment in self.comments_data:
            if (keyword.lower() in comment.get('comment', '').lower() or 
                keyword.lower() in comment.get('nickname', '').lower()):
                matched_comments.append(comment)
        
        # 显示匹配结果
        self.update_display_data(matched_comments)
        self.log_message(f"搜索 '{keyword}' - 找到 {len(matched_comments)} 条评论\n")
        
    def clear_search(self):
        """清除搜索"""
        self.search_var.set("")
        self.update_display_data(self.comments_data)
        
    def select_all_items(self):
        """全选所有项目"""
        for item in self.result_tree.get_children():
            self.result_tree.selection_add(item)
        
    def deselect_all_items(self):
        """取消全选所有项目"""
        self.result_tree.selection_remove(self.result_tree.selection())
        
    def invert_selection(self):
        """反选项目"""
        all_items = set(self.result_tree.get_children())
        selected_items = set(self.result_tree.selection())
        unselected_items = all_items - selected_items
        
        # 清除当前选择
        self.result_tree.selection_remove(self.result_tree.selection())
        
        # 选择未选中的项目
        for item in unselected_items:
            self.result_tree.selection_add(item)
        
    def delete_selected_items(self, event=None):
        """删除选中的项目"""
        selected_items = self.result_tree.selection()
        
        if not selected_items:
            messagebox.showwarning("警告", "请选择要删除的项目")
            return
            
        # 确认删除
        if messagebox.askyesno("确认删除", f"确定要删除选中的 {len(selected_items)} 项吗？"):
            # 获取所有项目并按在Treeview中的顺序排序
            all_items = list(self.result_tree.get_children())
            
            # 创建一个集合存储要删除的索引
            indices_to_delete = set()
            
            # 获取选中项的索引
            for item in selected_items:
                tags = self.result_tree.item(item, 'tags')
                if tags and len(tags) > 0:
                    try:
                        index = int(tags[0])
                        # 确保索引在有效范围内
                        if 0 <= index < len(self.comments_data):
                            indices_to_delete.add(index)
                        else:
                            # 如果索引超出范围，使用在Treeview中的位置
                            try:
                                index = all_items.index(item)
                                if 0 <= index < len(self.comments_data):
                                    indices_to_delete.add(index)
                            except ValueError:
                                self.log_message(f"警告: 无法确定项目 {item} 的索引\n")
                    except (ValueError, IndexError) as e:
                        # 如果无法从tags获取索引，则使用在Treeview中的位置
                        try:
                            index = all_items.index(item)
                            if 0 <= index < len(self.comments_data):
                                indices_to_delete.add(index)
                            else:
                                self.log_message(f"警告: 项目 {item} 的索引 {index} 超出范围\n")
                        except ValueError:
                            self.log_message(f"警告: 无法确定项目 {item} 的位置\n")
                else:
                    # 如果没有标签，使用在Treeview中的位置
                    try:
                        index = all_items.index(item)
                        if 0 <= index < len(self.comments_data):
                            indices_to_delete.add(index)
                        else:
                            self.log_message(f"警告: 项目 {item} 的索引 {index} 超出范围\n")
                    except ValueError:
                        self.log_message(f"警告: 无法确定项目 {item} 的位置\n")
            
            # 记录要删除的索引
            self.log_message(f"准备删除索引: {sorted(indices_to_delete)}\n")
            
            # 删除对应的图片缓存
            for index in sorted(indices_to_delete, reverse=True):
                if index in self.avatar_cache:
                    del self.avatar_cache[index]
             
            # 创建一个新的数据列表，排除要删除的项目
            remaining_data = []
            for i in range(len(self.comments_data)):
                if i not in indices_to_delete:
                    remaining_data.append(self.comments_data[i])
            
            # 更新comments_data为仅包含保留的数据
            self.comments_data[:] = remaining_data
            
            # 直接删除Treeview中的选中项
            for item in selected_items:
                self.result_tree.delete(item)
            
            # 重新为剩余项目设置正确的标签
            for i, item in enumerate(self.result_tree.get_children()):
                # 更新每个项目的标签为新的索引
                self.result_tree.item(item, tags=(i,))
            
            self.log_message(f"已删除 {len(selected_items)} 项，剩余 {len(self.comments_data)} 项\n")
            # 更新计数显示
            current_count = len(self.result_tree.get_children())
            self.item_count_label.config(text=f"显示: {current_count} 条")
            
            # 更新控制按钮状态
            self.update_control_buttons()
        
    def export_to_excel(self):
        """导出数据到Excel文件"""
        if not self.comments_data:
            messagebox.showwarning("警告", "没有数据可以导出")
            return
            
        # 选择保存位置
        file_path = filedialog.asksaveasfilename(
            defaultextension=".xlsx",
            filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")],
            title="导出为Excel文件"
        )
        
        if not file_path:
            return
            
        try:
            # 准备导出数据，过滤掉不能序列化的对象
            export_data = []
            for index, item in enumerate(self.comments_data):
                # 创建一个新字典，只包含基本数据类型
                clean_item = {}
                for key, value in item.items():
                    # 跳过不能序列化的对象（如图像对象）
                    if isinstance(value, (str, int, float, bool)) or value is None:
                        clean_item[key] = value
                    elif isinstance(value, (list, dict)):
                        # 对于列表和字典，转换为字符串
                        clean_item[key] = str(value)
                    # 其他对象类型跳过不处理
                
                # 确保字段名是标准的
                export_item = {
                    '昵称': clean_item.get('nickname', ''),
                    '评论': clean_item.get('comment', ''),
                    '时间': clean_item.get('time', ''),
                    '用户链接': clean_item.get('user_link', ''),
                    '头像URL': clean_item.get('avatar_url', '')
                }
                export_data.append(export_item)
            
            # 导出为Excel并插入图片
            import openpyxl
            from openpyxl.drawing.image import Image as ExcelImage
            from openpyxl.utils import get_column_letter
            
            # 创建工作簿和工作表
            wb = openpyxl.Workbook()
            ws = wb.active
            ws.title = "抖音评论数据"
            
            # 写入表头
            headers = ['头像', '用户链接', '评论', '时间', '昵称', '头像URL']
            for col, header in enumerate(headers, 1):
                cell = ws.cell(row=1, column=col, value=header)
                cell.font = openpyxl.styles.Font(bold=True)
            
            # 写入数据
            for row, item in enumerate(export_data, 2):
                # 用户链接
                user_link = item.get('用户链接', '')
                ws.cell(row=row, column=2, value=user_link)
                
                # 评论
                ws.cell(row=row, column=3, value=item.get('评论', ''))
                # 时间
                ws.cell(row=row, column=4, value=item.get('时间', ''))
                # 昵称
                ws.cell(row=row, column=5, value=item.get('昵称', ''))
                # 头像URL
                ws.cell(row=row, column=6, value=item.get('头像URL', ''))
                
                # 插入头像图片 - 使用用户链接作为文件名
                if user_link:
                    # 清理文件名中的非法字符以匹配保存时的文件名
                    safe_filename = "".join(c for c in user_link if c.isalnum() or c in (' ', '-', '_')).rstrip()
                    if safe_filename:
                        image_path = os.path.join(self.images_dir, f"{safe_filename}.jpg")
                    else:
                        # 如果用户链接为空或无效，回退到使用索引
                        image_index = row - 2
                        image_path = os.path.join(self.images_dir, f"{image_index}.jpg")
                else:
                    # 如果没有用户链接，使用索引
                    image_index = row - 2
                    image_path = os.path.join(self.images_dir, f"{image_index}.jpg")
                    
                if os.path.exists(image_path):
                    try:
                        # 插入图片
                        img = ExcelImage(image_path)
                        img.width = 100  # 设置图片宽度至少为100
                        img.height = 100  # 设置图片高度至少为100
                        
                        # 将图片插入到对应的单元格
                        ws.add_image(img, f'A{row}')
                    except Exception as e:
                        self.log_message(f"插入图片失败 (行 {row}): {e}\n")
            
            # 调整列宽和行高
            ws.column_dimensions['A'].width = 15  # 头像列
            ws.column_dimensions['B'].width = 20  # 昵称列
            ws.column_dimensions['C'].width = 30  # 评论列
            ws.column_dimensions['D'].width = 15  # 时间列
            ws.column_dimensions['E'].width = 30  # 用户链接列
            ws.column_dimensions['F'].width = 50  # 头像URL列
            
            # 调整行高以适应图片
            for row in range(2, len(export_data) + 2):
                ws.row_dimensions[row].height = 75  # 设置行高以适应100x100的图片
            
            # 保存文件
            wb.save(file_path)
            self.log_message(f"数据已成功导出到: {file_path}\n")
            messagebox.showinfo("成功", f"数据已成功导出到:\n{file_path}")
        except Exception as e:
            self.log_message(f"导出文件失败: {e}\n")
            import traceback
            self.log_message(f"详细错误信息: {traceback.format_exc()}\n")
            messagebox.showerror("错误", f"导出文件失败:\n{str(e)}")
    
    def get_avatar_image(self, avatar_url, index):
        """获取头像图像（与原版相同）"""
        if not avatar_url or avatar_url == "无头像" or avatar_url.strip() == "":
            return None
            
        try:
            # 检查是否已经缓存了图像数据
            cache_key = index  # 使用index作为缓存键
            if cache_key in self.avatar_cache and isinstance(self.avatar_cache[cache_key], tuple):
                # 如果缓存中已经有图像数据和PhotoImage，直接返回PhotoImage
                _, photo = self.avatar_cache[cache_key]
                return photo
            
            # 设置请求头
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
                'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
                'Accept-Encoding': 'gzip, deflate, br',
                'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
                'Referer': 'https://www.tiktok.com/'
            }
            
            # 创建请求对象
            request = urllib.request.Request(avatar_url, headers=headers)
            
            # 增加超时设置到30秒，并添加重试机制
            max_retries = 3
            for attempt in range(max_retries):
                try:
                    with urllib.request.urlopen(request, timeout=30) as response:
                        if response.getcode() != 200:
                            raise Exception(f"HTTP {response.getcode()}")
                            
                        image_data = response.read()
                        if len(image_data) == 0:
                            raise Exception("空的图像数据")
                    # 如果成功，跳出重试循环
                    break
                except Exception as e:
                    if attempt < max_retries - 1:  # 如果不是最后一次尝试，继续重试
                        time.sleep(2 ** attempt)  # 指数退避
                        continue
                    else:  # 如果是最后一次尝试，重新抛出异常
                        raise e
            
            # 保存图像到本地images目录，使用用户链接作为文件名
            try:
                # 获取用户链接作为文件名
                if index < len(self.comments_data):
                    user_link = self.comments_data[index].get('user_link', f'user_{index}')
                    # 清理文件名中的非法字符
                    safe_filename = "".join(c for c in user_link if c.isalnum() or c in (' ', '-', '_')).rstrip()
                    # 如果清理后的文件名为空，则使用索引
                    if not safe_filename:
                        safe_filename = str(index)
                else:
                    safe_filename = str(index)
                    
                image_filename = f"{safe_filename}.jpg"
                image_path = os.path.join(self.images_dir, image_filename)
                with open(image_path, 'wb') as f:
                    f.write(image_data)
            except Exception as e:
                if logger:
                    logger.error(f"保存头像文件失败 - URL: {avatar_url}, 错误: {e}")
                # 即使保存失败，也继续处理图像显示
            
            # 创建图片对象
            image = Image.open(io.BytesIO(image_data))
            
            # 统一转换为RGB模式
            if image.mode in ('RGBA', 'LA', 'P'):
                background = Image.new('RGB', image.size, (255, 255, 255))
                if image.mode == 'P':
                    image = image.convert('RGBA')
                background.paste(image, mask=image.split()[-1] if image.mode == 'RGBA' else None)
                image = background
            
            # 定义头像尺寸
            size = (100, 100)
            
            # 调整图像大小
            image = image.resize(size, Image.Resampling.LANCZOS)
            
            # 转换为Tkinter可用的PhotoImage
            photo = ImageTk.PhotoImage(image)
            
            # 缓存图像数据和PhotoImage，使用item ID作为键
            # 保存为元组(image_data, photo)格式，便于AI识别直接使用
            self.avatar_cache[cache_key] = (image_data, photo)
            return photo
            
        except Exception as e:
            if logger:
                logger.error(f"加载头像失败 - URL: {avatar_url}, 错误: {e}")
            return None
    
    def on_closing(self):
        """窗口关闭时的处理"""
        # 停止所有任务
        if self.is_processing and self.threaded_crawler:
            self.threaded_crawler.stop_all_tasks()
            
        # 取消更新循环
        if self._update_job:
            self.root.after_cancel(self._update_job)
            
        # 关闭窗口
        self.root.destroy()


def main():
    """主函数"""
    root = tk.Tk()
    app = EnhancedDouyinCrawlerGUI(root)
    
    # 设置关闭处理
    root.protocol("WM_DELETE_WINDOW", app.on_closing)
    
    root.mainloop()


if __name__ == "__main__":
    main()